﻿using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Validation.ValidationRules.Common;

namespace Sustainalytics.Companies
{
    public class ValidationRules : IValidationRules
    {
        private readonly string warningMessage;
        private readonly List<string> warningList;
        private readonly List<WhiteListLine> addToWhiteList;
        private readonly List<WhiteList> deleteFromWhiteList;
        private readonly IMongoCollection<WhiteList> _whiteListCollection;
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        public static IValidationRules GetInstance(IMongoCollection<WhiteList> whiteListCollection, IMongoCollection<CorporateData> corporateDataCollection)
        {
            return new ValidationRules(whiteListCollection, corporateDataCollection);
        }

        public ValidationRules(IMongoCollection<WhiteList> whiteListCollection, IMongoCollection<CorporateData> corporateDataCollection)
        {
            warningMessage = "Incorrect format at line {0}.";
            warningList = new List<string>();
            addToWhiteList = new List<WhiteListLine>();
            deleteFromWhiteList = new List<WhiteList>();
            _whiteListCollection = whiteListCollection;
            _corporateDataCollection = corporateDataCollection;
   
        }

        public List<string> ProcessFile(byte[] fileData)
        {
            var strFileData = System.Text.Encoding.UTF8.GetString(fileData, 0, fileData.Length);
            var lines = strFileData.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

            for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
                ProcessLine(lines[lineIndex], lineIndex + 1);

            if (deleteFromWhiteList.Any())
            {
                var filters = new List<FilterDefinition<WhiteList>>();
                var removeDuplicates = deleteFromWhiteList.GroupBy(x => new { x.CapitalIqId, x.ResearchProduct }).Select(group => group.First()).ToList();

                foreach (var whiteListItem in removeDuplicates)
                    filters.Add(Builders<WhiteList>.Filter.Where(x => x.CapitalIqId == whiteListItem.CapitalIqId && x.ResearchProduct == whiteListItem.ResearchProduct));

                _whiteListCollection.DeleteMany(Builders<WhiteList>.Filter.Or(filters));
            }

            if (addToWhiteList.Any())
            {
                var removeDuplicates = addToWhiteList.GroupBy(x => new { x.WhiteListItem.CapitalIqId, x.WhiteListItem.ResearchProduct }).Select(group => group.First()).ToList();
                var capitalIqIdsToAdd = removeDuplicates.Select(x => x.WhiteListItem.CapitalIqId);
                var existingWhitelist = _whiteListCollection.Find(Builders<WhiteList>.Filter.Empty).ToList();

                var corporateDataCapitalIqIds =_corporateDataCollection.Find(x => capitalIqIdsToAdd.Contains(x.CapitalIqId))
                .Project(x => x.CapitalIqId)
                .ToList();

                var validWhiteListToAdd = removeDuplicates
                    .Where(addedItem => 
                        corporateDataCapitalIqIds.Contains(addedItem.WhiteListItem.CapitalIqId) &&
                            !existingWhitelist
                                .Any(existingItem =>
                                    existingItem.CapitalIqId == addedItem.WhiteListItem.CapitalIqId &&
                                    existingItem.ResearchProduct == addedItem.WhiteListItem.ResearchProduct))
                    .Select(x => x.WhiteListItem)
                    .ToList();

                if (validWhiteListToAdd.Any())
                    _whiteListCollection.InsertMany(validWhiteListToAdd);

                addToWhiteList.Where(x => !corporateDataCapitalIqIds.Contains(x.WhiteListItem.CapitalIqId))
                    .ToList().ForEach(x => warningList.Add($"CapitalIqId {x.WhiteListItem.CapitalIqId} not found in CorporateData at line {x.LineNumber}"));
            }

            return warningList;
        }

        private void ProcessLine(string line, int lineNumber)
        {
            var items = line.Split(',');
            if (items.Length == 3)
            {
                int capitalIqId;
                var actionString = items[0].Trim();
                var capitalIqIdString = items[1].Trim();
                var researchProductString = items[2].Trim();

                if (!Enum.GetNames(typeof(Operation)).Any(x => x.ToLowerInvariant() == actionString.ToLowerInvariant())
                    || (!int.TryParse(capitalIqIdString, out capitalIqId))
                    || !Enum.GetNames(typeof(BusinessResearchProduct)).Any(x => x.ToLowerInvariant() == researchProductString.ToLowerInvariant()))
                {
                    warningList.Add(string.Format(warningMessage, lineNumber));
                    return;
                }

                var whiteListItem = new WhiteList
                {
                    Id = new Guid(),
                    CapitalIqId = capitalIqId,
                    ResearchProduct = (BusinessResearchProduct)Enum.Parse(typeof(BusinessResearchProduct), researchProductString, true)
                };

                var operation = (Operation)Enum.Parse(typeof(Operation), actionString, true);

                switch (operation)
                {
                    case Operation.Add: addToWhiteList.Add(new WhiteListLine { LineNumber = lineNumber, WhiteListItem = whiteListItem }); break;
                    case Operation.Delete: deleteFromWhiteList.Add(whiteListItem); break;
                }
            }
            else
                warningList.Add(string.Format(warningMessage, lineNumber));
        }

        private class WhiteListLine
        {
            public int LineNumber { get; set; }
            public WhiteList WhiteListItem { get; set; }
        }

        private enum Operation
        {
            Add,
            Delete
        }
    }
}
